Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add share/unshare for class objects #180

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Conversation

chenyan-dfinity
Copy link
Contributor

@chenyan-dfinity chenyan-dfinity commented Sep 22, 2020

share/unshare function for RBTree, HashMap and Buffer. This is useful for supporting upgrade, which only works for shareable types.

@chenyan-dfinity chenyan-dfinity marked this pull request as ready for review September 22, 2020 03:01
Copy link
Contributor

@matthewhammer matthewhammer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings about the unsafe route to create the object.

What's the use case you had in mind? (Why not wait until we can check the invariants?)

(A.freeze table, _count)
};

/// Put purely-functional representation into class. Need to make sure the tree is constructed with the same Eq and Hash functions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote that we either write those checking functions and use them, or remove this until we have them?

Is this a performance thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main use case is for upgrade. We cannot make HashMap stable, so the upgrade code would look like:

  stable var pureHashMap : HashMap.Tree = [];
  flexible let db: HashMap.HashMap = HashMap.unshareUnsafe(pureHashMap);
  system func preupgrade() {
   pureHashMap := HashMap.share();
  };

The db can be very large when upgrade, it's probably too expensive to check for the invariants.Also for the upgrade case, the invariants always hold.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

This is another use case for types that hide internal data but are not object types. (existential of some flavor? @crusso @rossberg ?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious how BigMap handles upgrade?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't handle it at the moment, but similar issues will arise (how to enforce invariants for the serialized representation without rechecking them).

Also, I think this question will arise for each developer, each time they implement such functions, for any canister with data invariants (so, almost all of them).

Perhaps we should add some notes in the design doc for share and unshare that just clarifies this design point (Do we expect these functions to be defensive and re-establish invariants, or to be trusting and assume that they hold, as with your unsafe variant?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/dfinity/motoko-base/blob/master/doc/design.md#classes

Currently reads as follows:

The share/unshare functions of a class need to convert to a type that is designed for stability (potentially, including extensibility) and space efficiency, not for enabling efficient direct operations.

If we follow this guide, it seems like we should be using a vector of key-value pairs for things like BigMap, and for other map-like structures, and shouldn't even include the hash values if they were computed for internal use only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, if at all possible, let's make such functions safe. Otherwise you break encapsulation. I doubt that the copying with freeze and thaw are significantly cheaper than a safe alternative.

Also, for polymorphic data structures, these functions need to be parameterised over the share/unshare functions for each type parameter, otherwise you cannot compose them properly.

if (count == 0) {
elems := [var];
};
elems := Prim.Array_init<X>(count, xs[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an else missing here? Otherwise this will trap when empty.

(A.freeze table, _count)
};

/// Put purely-functional representation into class. Need to make sure the tree is constructed with the same Eq and Hash functions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, if at all possible, let's make such functions safe. Otherwise you break encapsulation. I doubt that the copying with freeze and thaw are significantly cheaper than a safe alternative.

Also, for polymorphic data structures, these functions need to be parameterised over the share/unshare functions for each type parameter, otherwise you cannot compose them properly.

Copy link
Contributor

@kritzcreek kritzcreek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaning up my review queue

@ggreif ggreif force-pushed the master branch 2 times, most recently from d52aecd to 08507fc Compare October 21, 2022 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants